package com.hk.core.autoconfigure.data.jpa;


import com.hk.commons.util.Constants;
import com.hk.core.authentication.api.SecurityContext;
import jakarta.persistence.EntityManager;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.hibernate.Session;

import java.math.BigInteger;
import java.util.Objects;

/**
 * <pre>
 *
 * jpa 数据权限过滤器切面
 * 主要是在 createEntityManager 方法创建 EntityManager 后，将登陆的用户数据权限值加入到 sql 的where 条件中.
 *
 *      使用方法:
 *          1、 Entity 需要集成 {@link com.hk.core.data.jpa.domain.AbstractDataRuleSnowflakeAuditable} 或 {@link com.hk.core.data.jpa.domain.AbstractDataRuleUUIDAuditable}
 *          2、 Entity 类上加 {@link org.hibernate.Filter} 注解
 *      <blockquote>
 *        <pre>
 *          Data
 *          Entity
 *          Filter(name = Constants.DATA_FILTER_NAME, condition = "(data_rule & :dataRule > 0)")
 *          Table(name = "sys_permission")
 *          EqualsAndHashCode(callSuper = true)
 *          public class SysPermission extends AbstractDataRuleSnowflakeAuditable {
 *
 *          }
 *
 *          上面 condition 的值 (data_rule & :dataRule > 0) 是重点， 这里使用 & 来计算用户是否有指定的权限：
 *              1、需要在保存数据的时候，就要根据角色/权限来计算该条记录需要访问的权限，
 *                     这里使用二进制(0/1)表示每个权限，0 表示不需要此权限，1表示需要此权限
 *                      如系统中有固定的角色 1: admin, 2: test,3:others 三个角色，这三个角色要给排序，如 第1位(个位)是admin,第2位(十位)是test, 第3位(百位)是others
 *                      如果这条保存的记录需要是 admin 或 test 才能查询，则该记录的权限值二进制值为  011
 *                          为什么是011？ 定义: 0表示没权限，1表示有权限，
 *                             第一位表示admin，有权限，则第一位是1
 *                             第二位表示test，有权限，则第二位是1
 *                             第二位表示others，无权限，则第三位是0
 *                      再将这个二进制的值转换为 十进制: Integer.parseInt("011",2)  ，此时，DB 的权限值就是 3
 *
 *                     如果系统有新的权限值进来，如 others2 ,则这个权限值的排序为 第4位(千位，以此自增，不能和已有重复)，且计算权限二进制值的时候，要就在最左边，
 *                      因为对于老数据来讲，这个新的权限值都是0，对于老数据才不会有影响
 *               2、 当前用户登陆后，需要根据用户的角色/权限，和系统已定义的权限排序值计算出用户的权限值。
 *                         如系统中定义了 1: admin, 2: test,3:others 三个角色
 *                         用户登陆后，只有 admin /test角色，
 *                         111 (系统存在的角色，应该全部都是1)
 *                         011  (用户登陆后的角色,个位为1，用户有 admin ，十位为1,用户有test，百位为0，用户没有others)
 *                         根据这个用户的角色计算权限值转十进制：Integer.parseInt("011",2)  ，此时，DB 的权限值就是 3
 *               3、通过这个Aspect，会将用户的权限值作为条件与记录中的权限值计算进行过滤
 *
 *        </pre>
 *      </blockquote>
 *      <pre>
 *      注意：
 *          1、 (data_rule & :dataRule > 0) 这个条件是大于0 ，只适用于记录所需要的权限值和登陆用户的权限值有一个交集即可 ；如果要全部匹配，则要使用 (data_rule <= :dataRule)
 *          2、使用Spring Data Jpa 中的  {@link org.springframework.data.jpa.repository.JpaRepository#findById(Object)} 方法时，       不会添加@Filter 条件
 *          3、使用Spring Data Jpa 中的  {@link org.springframework.data.jpa.repository.JpaRepository#getReferenceById(Object)} 方法时，     不会添加@Filter 条件
 *          4、在Spring Data Jpa 中 使用  {@link org.springframework.data.jpa.repository.Query}，注意，且使用 nativeQuery = true 时  不会添加@Filter 条件
 *      </pre>
 * </pre>
 *
 * @author Kevin
 */
@Aspect
record EnableFilterAspect(SecurityContext securityContext) {

    @AfterReturning(pointcut = "bean(entityManagerFactory) && execution(* createEntityManager(..))",
            returning = "entityManager")
    public void getSessionAfter(EntityManager entityManager) {
        if (securityContext.isAuthenticated()) {
            var principal = securityContext.getPrincipal();
            var dataRule = principal.getDataRule();
            if (Objects.nonNull(dataRule) && dataRule.compareTo(BigInteger.ZERO) > -1) {
                var filter = entityManager.unwrap(Session.class).enableFilter(Constants.DATA_FILTER_NAME);
                filter.setParameter("dataRule", dataRule);// 这里 unwrap方法获取的Session对象不需要关闭，Spring 会自动关闭会话
//                try (var session = entityManager.unwrap(Session.class)) {
//                    session.enableFilter(Constants.DATA_FILTER_NAME).setParameter("dataRule", dataRule);
//                }
            }
        }
    }

}
